/**
 * @file gvozdarev_1_a.c
 * @author Gvozdarev Sergey, gr016 MIPT
 * MIT license
 */

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <malloc.h>
#include "mpi.h"

#define __DEBUG
#define MAT_SIZE 500

#define G_ELEM_NUM 1
#define G_FINISH   -1
#define G_MAIN_RANK 0

#define real_t double

main( int argc, char ** argv)  
{
    real_t tsend_begin, tsend_end;
    real_t tssend_begin, tssend_end;
    real_t tbsend_begin, tbsend_end;

    MPI_Status status;
	
    int comm_rank,comm_size;
    int mess_size, mess_num;


    if ( argc != 3)
    {
        printf( "Number of message and size must be defined\n");
        return 1;
    }

    int NOK  =  sscanf( argv[ 1], "%d", &mess_num) != 1 || mess_num  < 1;
        NOK |= sscanf( argv[ 2], "%d", &mess_size) != 1 || mess_size < 1;

    if ( NOK)
    {
        printf( "Incorrect\n");
    }

	//// Init
    MPI_Init( &argc, &argv ); 

	MPI_Comm_rank( MPI_COMM_WORLD, &comm_rank );
    MPI_Comm_size( MPI_COMM_WORLD, &comm_size );
   
    char *buf = ( char*)malloc( sizeof( char) * mess_size);
    
    if ( comm_rank == 0) // main process
    {
        int i; 
        // MPI_Send ==========================
        tsend_begin = MPI_Wtime();

        for ( i = 0; i < mess_num; i++)
        {
            MPI_Recv( buf, mess_size, MPI_CHAR, comm_rank + 1, G_ELEM_NUM, MPI_COMM_WORLD, &status);
            MPI_Send( buf, mess_size, MPI_CHAR, comm_rank + 1, G_ELEM_NUM, MPI_COMM_WORLD);
        }
        tsend_end   = MPI_Wtime();
        MPI_Barrier( MPI_COMM_WORLD);
        
        // MPI_Ssend ===========================
        tssend_begin = MPI_Wtime();

        for ( i = 0; i < mess_num; i++)
        {
            MPI_Recv ( buf, mess_size, MPI_CHAR, comm_rank + 1, G_ELEM_NUM, MPI_COMM_WORLD, &status);
            MPI_Ssend( buf, mess_size, MPI_CHAR, comm_rank + 1, G_ELEM_NUM, MPI_COMM_WORLD);
        }
        tssend_end   = MPI_Wtime();

        // MPI_Bsend ============================
        int size = sizeof( char) * mess_num + MPI_BSEND_OVERHEAD; 
        char *mpi_buf  = (char*)malloc( size);
        MPI_Barrier( MPI_COMM_WORLD);
       
        tbsend_begin = MPI_Wtime();
        for ( i = 0; i < mess_num; i++)
        {
            MPI_Recv( buf, mess_size, MPI_CHAR, comm_rank + 1, G_ELEM_NUM, MPI_COMM_WORLD, &status);
            
            MPI_Buffer_attach( mpi_buf, size); 
            MPI_Bsend( buf, mess_size, MPI_CHAR, comm_rank + 1, G_ELEM_NUM, MPI_COMM_WORLD);
            MPI_Buffer_detach( &mpi_buf, &size );
        }
        tbsend_end   = MPI_Wtime();
        
        free( mpi_buf);
        MPI_Barrier( MPI_COMM_WORLD);
        // =======================================
    }
    else // workers
    {
        int i; 
        // MPI_Send ==========================

        for ( i = 0; i < mess_num; i++)
        {
            MPI_Send( buf, mess_size, MPI_CHAR, comm_rank - 1, G_ELEM_NUM, MPI_COMM_WORLD);
            MPI_Recv( buf, mess_size, MPI_CHAR, comm_rank - 1, G_ELEM_NUM, MPI_COMM_WORLD, &status);
        }
        MPI_Barrier( MPI_COMM_WORLD);
        
        // MPI_Ssend ===========================

        for ( i = 0; i < mess_num; i++)
        {
            MPI_Ssend( buf, mess_size, MPI_CHAR, comm_rank - 1, G_ELEM_NUM, MPI_COMM_WORLD);
            MPI_Recv ( buf, mess_size, MPI_CHAR, comm_rank - 1, G_ELEM_NUM, MPI_COMM_WORLD, &status);
        }

        // MPI_Bsend ============================
        int size = sizeof( char) * mess_num + MPI_BSEND_OVERHEAD; 
        char * mpi_buf = (char*)malloc( size);
        MPI_Barrier( MPI_COMM_WORLD);

        for ( i = 0; i < mess_num; i++)
        {
            MPI_Buffer_attach( mpi_buf, size); 
            MPI_Bsend( buf, mess_size, MPI_CHAR, comm_rank - 1, G_ELEM_NUM, MPI_COMM_WORLD);
            MPI_Buffer_detach( &mpi_buf, &size);
            
            MPI_Recv( buf, mess_size, MPI_CHAR, comm_rank - 1, G_ELEM_NUM, MPI_COMM_WORLD, &status);
        }
        
        free( mpi_buf);
        MPI_Barrier( MPI_COMM_WORLD);
        

        // =======================================
        free( buf);
        MPI_Finalize();
        return 0;
    }

    printf( "Result: %d %d %d\n", (int)( 1000000*( tsend_end - tsend_begin)), (int)( 1000000*( tssend_end - tssend_begin)), (int)( 1000000*(( tbsend_end - tbsend_begin))));
   
    free( buf);
    MPI_Finalize();
    return 0;
} 
